package edu.uvic.TabViz.framework.primitives;

import java.io.Serializable;

import android.graphics.Point;
import android.opengl.Matrix;

public class Camera implements Serializable{
	
	private static final long serialVersionUID = 1611760562142806186L;
	protected Point3D position, at, rotation;
	protected float near = 10.0f;
	protected float far = 1000.0f;
	protected float fieldOfView = 45;

	float[] viewMatrix = new float[16];
	float[] rotationMatrix = new float[16];
	float[] projectionMatrix = new float[16];

	public Camera() {
		position = new Point3D();
		rotation = new Point3D();
	}
	
	public Camera(Camera camera){
		position = new Point3D(camera.getPosition());
		rotation = new Point3D(camera.getRotation());
		if (camera.at != null)
			at = new Point3D(camera.getAt());
		this.near = camera.near;
		this.far = camera.far;
		this.fieldOfView = camera.fieldOfView;
		this.viewMatrix = camera.getViewMatrix();
		this.rotationMatrix = camera.getRotationMatrix();
		this.projectionMatrix = camera.getProjectionMatrix();
	}
	public Camera clone(){
		Camera c = new Camera(); 
		c.position = new Point3D(getPosition());
		c.rotation = new Point3D(getRotation());
		if (at != null)
			c.at = new Point3D(getAt());
		c.near = near;
		c.far = far;
		c.fieldOfView = fieldOfView;
		return c;
	}
	public float[] getViewMatrix() {
		float[] matrix = new float[16];
		if (at != null) 
			Matrix.setLookAtM(viewMatrix, 0, position.x, position.y, position.z, at.x, at.y, at.z, 0f, 1.0f, 0.0f);
		else {
			Matrix.setIdentityM(rotationMatrix, 0);
			rotate(rotationMatrix, 0, -rotation.x, 1.0f, 0.0f, 0.0f);
			rotate(rotationMatrix, 0, -rotation.y + 180, 0.0f, 1.0f, 0.0f);
			rotate(rotationMatrix, 0, -rotation.z, 0.0f, 0.0f, 1.0f);
			Matrix.setIdentityM(matrix, 0);
			Matrix.setIdentityM(viewMatrix, 0);
			Matrix.translateM(matrix, 0, -position.x, -position.y, -position.z);
			Matrix.multiplyMM(viewMatrix, 0, rotationMatrix, 0, matrix, 0);
		}
		return viewMatrix;
	}

	public void rotate(float[] rotationMatrix, int mOffset, float a, float x, float y,
			float z) {
		float[] matrix = new float[16];
		float[] matrix2 = new float[16];
		Matrix.setIdentityM(matrix, 0);
		Matrix.setRotateM(matrix, 0, a, x, y, z);
		System.arraycopy(rotationMatrix, 0, matrix2, 0, 16);
		Matrix.multiplyMM(rotationMatrix, mOffset, matrix2, mOffset, matrix, 0);
	}

	public Point3D getPosition() {
		return position;
	}

	public void setPosition(Point3D position) {
		this.position = position;
	}

	public Point3D getAt() {
		return at;
	}

	public void setAt(Point3D at) {
		this.at = at;
	}

	public Point3D getRotation() {
		return rotation;
	}

	public void setRotation(Point3D rotation) {
		this.rotation = rotation;
	}

	public float getNear() {
		return near;
	}

	public void setNear(float near) {
		this.near = near;
	}

	public float getFar() {
		return far;
	}

	public void setFar(float far) {
		this.far = far;
	}

	public float getFieldOfView() {
		return fieldOfView;
	}

	public void setFieldOfView(float fieldOfView) {
		this.fieldOfView = fieldOfView;
	}

	public float[] getRotationMatrix() {
		return rotationMatrix;
	}

	public void setRotationMatrix(float[] rotationMatrix) {
		this.rotationMatrix = rotationMatrix;
	}

	public float[] getProjectionMatrix() {
		return projectionMatrix;
	}

	public void setProjectionMatrix(Point size) {
		float ratio = (float) size.x / size.y;
		float frustumH = (float) Math.tan(getFieldOfView() / 360.0 * Math.PI) * getNear();
		//float frustumH = getNear();
		float frustumW = frustumH * ratio;
		Matrix.frustumM(projectionMatrix, 0, -frustumW, frustumW, -frustumH, frustumH, getNear(), getFar());
	}
	
	public void setProjectionMatrix(float[] projectionMatrix) {
		this.projectionMatrix = projectionMatrix;
	}

	public void setViewMatrix(float[] viewMatrix) {
		this.viewMatrix = viewMatrix;
	}
	@Override
	public int hashCode() {
		return ((at!=null)? at.hashCode(): 0) + rotation.hashCode() + position.hashCode() + (int) (far + near + fieldOfView) ;
	}
	
}
